export  function clone<T>(source:unknown, existed?:WeakMap<object,unknown>):T {
  if (!source) return source as T
  if (!source || typeof source !== 'object') return source as T
  if (source instanceof Date) return new Date(source) as T
  if (source instanceof RegExp) return new RegExp(source) as T
  if (source instanceof Error) source as T
  if (source instanceof Map) return new Map(source) as T
  if (source instanceof Set) return new Set(source) as T
  if (source instanceof Promise) return Promise.resolve(source) as T
  if (source instanceof Function) return source as T
  if (source instanceof Symbol) return source as T
  if (source instanceof BigInt) return source as T
  if (source instanceof Boolean) return source as T
  if (source instanceof Number) return source as T
  if (source instanceof String) return source as T
  
  if (!existed) existed = new WeakMap()
  else if (existed.has(source)) return existed.get(source) as T

  const result = Array.isArray(source) ? [] : {}
  existed.set(source, result)

  for (const key in source) {
    (result as any)[key] = clone((source as any)[key], existed)
  }

  return result as T
} 
let deepLimit = 20

function mergeObjects(sourceObj:object,targetObj:object,deep?:number ){
  for (const key in sourceObj) {
    const sourceValue = (sourceObj as any)[key]
    const targetValue = (targetObj as any)[key] as any
    (targetObj as any)[key]=merge(sourceValue,targetValue,deep===undefined?0:deep+1)
  }
  return targetObj
}
function mergeMaps(sourceMap:Map<any,any>,targetMap:Map<any,any>,deep?:number){
  for (const [key,value] of sourceMap) {
    targetMap.set(key,merge(value,targetMap.get(key),deep===undefined?0:deep+1))
  }
  return targetMap
}
function mergeObjectToMap(sourceObj:object,targetMap:Map<any,any>,deep?:number){
  for (const key in sourceObj) {
    targetMap.set(key,merge((sourceObj as any)[key],targetMap.get(key),deep===undefined?0:deep+1))
  }
  return targetMap
}
function mergeMapToObject(sourceMap:Map<any,any>,targetObj:object,deep?:number){
  for (const [key,value] of sourceMap) {
    (targetObj as any)[key]=merge(value,(targetObj as any)[key],deep===undefined?0:deep+1)
  }
  return targetObj
}


/**
 * assign source to target
 * + the main feature is merge source to target  and return target
 * 
 * @export
 * @template TTarget
 * @param {*} source
 * @param {TTarget} target
 * @param {number} [deep]
 * @return {*}  {TTarget}
 */
export function merge<TTarget=any>(source:any,target:TTarget,deep?:number):TTarget{
  if (deep!==undefined && deep>deepLimit) throw new Error('deepLimit')  
  
  const sourceType= typeof source
  const targetType= typeof target
  if (sourceType!=='object') {
    return clone(source)
  }

  // now the  source is always object
  if (targetType!=='object'|| !target) return clone(source)
  if (!source) return source
  if (source instanceof Date) return new Date(source) as TTarget
  if (source instanceof RegExp) return new RegExp(source) as TTarget
  if (source instanceof Error) return source as TTarget
  if (source instanceof Promise) return Promise.resolve(source) as TTarget
  

  // now the target & source is always object
  if (source instanceof Map) {
    if (target instanceof Map) {
      return mergeMaps(source,target,deep)  as  TTarget
    }else {
      return mergeMapToObject(source,target,deep) as TTarget
    }
  }else {
    if (target instanceof Map) {
      return mergeObjectToMap(source,target,deep) as TTarget
    }else {
      return mergeObjects(source,target,deep) as TTarget
    }
  }
  
}
